#pragma once

extern CRender Renderer;

enum TextureFilter 
{
	TF_NONE=0,
	TF_BILINEAR,
	TF_TRILINEAR,
	TF_ANISOTROP
};

enum LoadFormat 
{
	CF_NULL=0,
	CF_DXT1C,
	CF_DXT1A,
	CF_DXT3,
	CF_DXT5,
	CF_A8,
	CF_L8,
	CF_AL8,
	CF_BGRA8,
	CF_BGR8,
	CF_BGR5A1,
	CF_BGR565,
	CF_BGRA4,
	CF_R16F,
	CF_R32F,
	CF_GR16F,
	CF_GR32F,
	CF_BGRA16F,
	CF_BGRA32F
};

struct sdds
{
	u32 magic;
	u32 size;
	u32 mflags;
	u32 height;
	u32 width;
	u32 polsize;
	u32 depth;
	u32 mipmapcount;
	u32 _somedata1[12];
	u32 flags;
	u32 fourcc;
	u32 rgbbpp;
	u32 rmask;
	u32 gmask;
	u32 bmask;
	u32 amask;
	u32 caps1;
	u32 caps2;
	u32 _somedata2[3];
};
enum
{
	FOURCC_DXT1          = 0x31545844,
	FOURCC_DXT3          = 0x33545844,
	FOURCC_DXT5          = 0x35545844,
	FOURCC_R16F          = 0x0000006F,
	FOURCC_G16R16F       = 0x00000070,
	FOURCC_A16B16G16R16F = 0x00000071,
	FOURCC_R32F          = 0x00000072,
	FOURCC_G32R32F       = 0x00000073,
	FOURCC_A32B32G32R32F = 0x00000074,

	DDPF_ALPHAPIXELS = 0x01,
	DDPF_ALPHA       = 0x02,
	DDPF_FOURCC      = 0x04,
	DDPF_RGB         = 0x40,
	DDPF_LUMINANCE   = 0x020000,
	DDSD_MIPMAPCOUNT = 0x020000,
	DDSCAPS2_CUBEMAP = 0x0200
};
struct load_format_struct
{
	bool compressed;
	byte div_size;
	byte bytes;
	u32	 iformat;
	u32	 eformat;
	u32	 data_type;
} load_format_data[lf_max] = \
{
	{false, 1, 1,  GL_FALSE,						GL_FALSE,			 GL_FALSE},
	{true,  4, 8,  GL_COMPRESSED_RGB_S3TC_DXT1_EXT,	GL_FALSE,			 GL_FALSE},
	{true,  4, 8,  GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,GL_FALSE,			 GL_FALSE},
	{true,  4, 16, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT,GL_FALSE,			 GL_FALSE},
	{true,  4, 16, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT,GL_FALSE,			 GL_FALSE},
	{false, 1, 1,  GL_ALPHA8,						GL_ALPHA,			 GL_UNSIGNED_BYTE},
	{false, 1, 1,  GL_LUMINANCE8,					GL_LUMINANCE,		 GL_UNSIGNED_BYTE},
	{false, 1, 2,  GL_LUMINANCE8_ALPHA8,			GL_LUMINANCE_ALPHA,  GL_UNSIGNED_BYTE},
	{false, 1, 4,  GL_RGBA8,						GL_BGRA_EXT,		 GL_UNSIGNED_BYTE},
	{false, 1, 3,  GL_RGB8,							GL_BGR_EXT,			 GL_UNSIGNED_BYTE},
	{false, 1, 2,  GL_RGB5_A1,						GL_BGRA_EXT,		 GL_UNSIGNED_SHORT_1_5_5_5_REV},
	{false, 1, 2,  GL_RGB5,							GL_RGB,				 GL_UNSIGNED_SHORT_5_6_5},
	{false, 1, 2,  GL_RGBA4,						GL_BGRA_EXT,		 GL_UNSIGNED_SHORT_4_4_4_4_REV},
	{false, 1, 2,  GL_R16F,							GL_RED,				 GL_HALF_FLOAT},
	{false, 1, 4,  GL_R32F,							GL_RED,				 GL_FLOAT},
	{false, 1, 4,  GL_RG16F,						GL_RG,				 GL_HALF_FLOAT},
	{false, 1, 8,  GL_RG32F,						GL_RG,				 GL_FLOAT},
	{false, 1, 8,  GL_RGBA16F,						GL_RGBA,			 GL_HALF_FLOAT},
	{false, 1, 16, GL_RGBA32F,						GL_RGBA,			 GL_FLOAT}
};
u32 filter_mode_data[2][4][2] =
		{{{GL_NEAREST, GL_NEAREST}, {GL_LINEAR, GL_LINEAR}, {GL_LINEAR, GL_LINEAR}, {GL_LINEAR, GL_LINEAR}},
		{{GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST}, {GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR}, {GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR}, {GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR}}};
load_format get_load_format(sdds& _dds)
{
	if(_dds.flags & DDPF_FOURCC)
	{
		switch(_dds.fourcc)
		{
		// Compressed
		case FOURCC_DXT1:
			if(_dds.flags ^ DDPF_ALPHAPIXELS > 0)
				return lf_dxt1a;
			else
				return lf_dxt1c;
		case FOURCC_DXT3: 
			return lf_dxt3;
		case FOURCC_DXT5: 
			return lf_dxt5;
		// Float
		case FOURCC_R16F:
			return lf_r16f;
		case FOURCC_G16R16F: 
			return lf_gr16f;
		case FOURCC_A16B16G16R16F: 
			return lf_bgra16f;
		case FOURCC_R32F:
			return lf_r32f;
		case FOURCC_G32R32F: 
			return lf_gr32f;
		case FOURCC_A32B32G32R32F: 
			return lf_bgra32f;
		}
	}
	else
	{
		switch(_dds.rgbbpp)
		{
		case 8:
			if((_dds.flags & DDPF_LUMINANCE)&&(_dds.rmask ^ 0xFF == 0))
			{
				return lf_l8;
			}
			else
			{
				if((_dds.flags & DDPF_ALPHA)&&(_dds.amask ^ 0xFF == 0))
					return lf_a8;
			}
			break;
		case 16:
			if(_dds.flags & DDPF_ALPHAPIXELS)
			{
				if((_dds.flags & DDPF_LUMINANCE)&&(_dds.rmask ^ 0xFF +_dds.amask ^ 0xFF00 == 0))
				{
					return lf_al8;
				}
				else
				{
					if(_dds.flags & DDPF_RGB)
					{
						if(_dds.rmask ^ 0x0F00 + _dds.gmask ^ 0x00F0 + _dds.bmask ^ 0x0F + _dds.amask ^ 0xF000 == 0)
						{
							return lf_bgra4;
						}
						else
						{
							if(_dds.rmask ^ 0x7C00 + _dds.gmask ^ 0x03E0 + _dds.bmask ^ 0x1F + _dds.amask ^ 0x8000 == 0)
								return lf_bgr5a1;
						}
					}
				}
			}
			else
			{
				if(_dds.flags & DDPF_RGB)
				{
					if(_dds.rmask ^ 0xF800 + _dds.gmask ^ 0x07E0 + _dds.bmask ^ 0x1F == 0)
						return lf_bgr565;
				}
			}
			break;
		case 24:
			if(_dds.rmask ^ 0xFF0000 + _dds.gmask ^ 0xFF00 + _dds.bmask ^ 0xFF == 0)
				return lf_bgr8;
			break;
		case 32:
			if(_dds.rmask ^ 0xFF0000 + _dds.gmask ^ 0xFF00 + _dds.bmask ^ 0xFF + _dds.amask ^ 0xFF000000 == 0)
				return lf_bgra8;
			break;
		}
	}
	return lf_null;
};

class texture
{
public:
	texture()
	{
		width  = 0;
		height = 0;
		id = -1;
	}
	texture(u32 _width,u32 _height,u32 _format = GL_RGBA8,void* _data=0)
	{
		width  = _width;
		height = _height;
		glGenTextures(1,&id);
		glBindTexture(GL_TEXTURE_2D,id);
		glTexImage2D(GL_TEXTURE_2D,0,_format,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,_data);
		sampler = GL_TEXTURE_2D;
		mipmap  = false;
		current_filter  = tf_none;
	}
	texture(const char* _fname)
	{
		load(_fname);
	}
	void load(const char* _fname)
	{
		width = 0;
		height = 0;

		stream str;
	
		u32 samples;
		sdds dds;

		if(!str.init(_fname))
		{
			ASSERT(false,"Unable open texture file");
			return;
		}
		str.read(&dds,sizeof(dds));
		width  = dds.width;
		height = dds.height;
		// Select OpenGL texture format
		load_format lf = get_load_format(dds);
		if(lf == lf_null)
		{
			ASSERT(false,"Unsupported texture format");
			str.close();
			return;
		}

		if((dds.mflags & DDSD_MIPMAPCOUNT) == 0)
			dds.mipmapcount = 1;

		u32 rmips = dds.mipmapcount;
		for(u32 i=0;i<dds.mipmapcount;i++)
		{
			if(min(width >> i, height >> i) < 4)
			{
				rmips = i;
				break;
			}
		}

		// 2D image
		sampler = GL_TEXTURE_2D;
		samples = 1;
		// CubeMap image
		//TODO
		// 3D image
		//TODO

		void* data = malloc((width/load_format_data[lf].div_size)*(height/load_format_data[lf].div_size)*load_format_data[lf].bytes);

		glGenTextures(1,&id);
		glBindTexture(sampler,id);

		for(u32 i=0;i<dds.mipmapcount;i++)
		{
			u32 w = width >> i;
			u32 h = height >> i;
			dds.size = ((w/load_format_data[lf].div_size)*(h/load_format_data[lf].div_size)*load_format_data[lf].bytes);
			if(i>=rmips)
			{
				str.pos(str.current_pos + dds.size);
				continue;
			}

			str.read(data,dds.size);
			if(load_format_data[lf].compressed)
			{
				ASSERT(gl.CompressedTexImage2D!=0,"Texture compression not supported");
				gl.CompressedTexImage2D(sampler,i,load_format_data[lf].iformat,w,h,0,dds.size,data);
			}
			else
				glTexImage2D(sampler,i,load_format_data[lf].iformat,w,h,0,load_format_data[lf].eformat,load_format_data[lf].data_type,data);
		}

		free(data);

		mipmap = (dds.mipmapcount>1);
		if(mipmap)
			glTexParameteri(sampler,GL_TEXTURE_MAX_LEVEL,rmips-1);

		str.close();
		filter(tf_anisotrop);
	}
	~texture()
	{
	}
	void filter(texture_filter _value)
	{		
		if(current_filter != _value)
		{
			current_filter = _value;
			bind(0);
			glTexParameteri(sampler,GL_TEXTURE_MIN_FILTER, filter_mode_data[mipmap][current_filter][0]);
			glTexParameteri(sampler,GL_TEXTURE_MAG_FILTER, filter_mode_data[mipmap][current_filter][1]);
			if(render.max_aniso>0)
			{
				if(current_filter == tf_anisotrop)
					glTexParameteri(sampler,GL_TEXTURE_MAX_ANISOTROPY_EXT,render.max_aniso);
				else
					glTexParameteri(sampler,GL_TEXTURE_MAX_ANISOTROPY_EXT,1);
			}
		}
	}
	void clamp(bool _value)
	{
	}
	void gen_mipmaps()
	{
	}
	void bind(u32 _level)
	{
	}

	u32				id;
	u32				width;
	u32				height;
	texture_filter  current_filter;
	bool			current_clamp;
	bool			mipmap;
	u32				sampler;
};